#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>

#include "main.h"
#include "reader.h"
#include "xmalloc.h"
#include "utils.h"


int
open_file(const char *file_path)
{
  int result = -1;

  if ( access(file_path, R_OK) == 0 )
  {
    if ( (result = open(file_path, O_RDONLY)) < 0 )
    {
      EF_errstr("Cant open file %s", file_path);
    }
  } else {
    EF_errstr("Cant access file %s", file_path);
  }

#ifdef ENABLE_DEBUG
  if (result >= 0)
  {
    DD("fd%d: file open: %s", result, file_path);
  }
#endif

  return (result);
}

#define MMAPFD_FAIL close(fd); return (NULL);
void *
mmap_fd(int fd, size_t *memsize)
{
  void *data_ptr = NULL;

  if (fd < 0 || !memsize) return (NULL);
  if ( (off_t)(*(memsize) = lseek(fd, 0, SEEK_END)) == -1 )
  {
    EF_errstr("fd%d: Cant seek end-of-file", fd);
    MMAPFD_FAIL
  }
  if ( *(memsize) == 0 )
  {
    EF("fd%d: file empty", fd);
    MMAPFD_FAIL
  }
  if ( (data_ptr = mmap(0, *(memsize), PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED )
  {
    EF_errstr("fd%d: Cant map file into memory", fd);
    MMAPFD_FAIL
  }

#ifdef ENABLE_DEBUG
  if (data_ptr)
  {
    DD("fd%d: mapped file into memory: %p", fd, data_ptr);
  }
#endif
  assert(data_ptr);
  return (data_ptr);
}

string_t *
mmap_fd_str(int fd)
{
  char *p = NULL;
  size_t siz = 0;
  string_t *str = NULL;

  if (fd < 0) return (NULL);
  p = (char *) mmap_fd(fd, &siz);
  if (p)
  {
    str = create_nstring(p, siz);
  } else return (NULL);
  munmap(p, siz);
  close(fd);
  return (str);
}

bool
mmap_data(dirlist_elem_t *elem)
{
  int fd;

  if (!elem || !elem->path) goto md_err;
  if ( (fd = open_file(get_string(elem->path))) < 0 ||
       (elem->data = mmap_fd_str(fd)) == NULL )
  {
    elem->data = NULL;
    goto md_err;
  }
  elem->siz = get_strlen(elem->data);
  return (true);

md_err:
  return (false);
}

dirlist_t *
listfiles(char dir_path[MAXPATHLEN+1], const char *ext, dirlist_t *dptr)
{
  DIR *dir = NULL;
  struct dirent *dirent = NULL;
  char obj[MAXPATHLEN+1], real_path[MAXPATHLEN+1], *tmp = NULL;
  dirlist_elem_t *elem = NULL;

  if (!dir_path) return (NULL);
  if ( rpath(dir_path, real_path) ) {
    strcpy(dir_path, real_path);
  } else return (NULL);

  if (!dptr)
  {
    dptr = array_create(sizeof(struct dirlist_elem), DIRLIST_INITSIZ);
  }

  if ( access(dir_path, R_OK | X_OK) == 0 )
  {
    if ( (dir = opendir(dir_path)) )
    {
      while ( (dirent = readdir(dir)) )
      {
        if ( strncmp(dirent->d_name, ".", MAXPATHLEN) == 0 ||
             strncmp(dirent->d_name, "..", MAXPATHLEN) == 0 ) continue;
        snprintf(obj, MAXPATHLEN, "%s/%s", dir_path, dirent->d_name);
        obj[MAXPATHLEN] = '\0';
        if (isdir(obj))
        {
          listfiles(obj, ext, dptr);
        } else {
          if (ext)
         {
            if ( (tmp = strrchr(obj, '.')) != NULL )
            {
              tmp++;
              if ( (strcmp(tmp, ext) != 0) || ( strlen(tmp) != strlen(ext) ))
              {
                continue;
              }
            } else {
              continue;
            }
          }
          elem = (dirlist_elem_t *)ncmalloc(1, sizeof(struct dirlist_elem));
          elem->ext = strdup(ext);
          elem->path = create_string(obj);
          array_add_data(dptr, (void *)elem, 1);
          ncfree(elem, sizeof(struct dirlist_elem));
        }
      }
    } else {
    }
  } else {
    EF_errstr("Cant access dir %s", dir_path);
  }

  closedir(dir);
  if (array_used_abs(dptr) == 0) {
    array_free(dptr);
    return (NULL);
  }
  return (dptr);
}

dirlist_elem_t *
listfiles_foreach(dirlist_t *dlist, size_t *idx_ptr)
{
  size_t i;
  dirlist_elem_t *elem = NULL;

  if(!dlist || !idx_ptr) return (NULL);
  i = (*idx_ptr);
  if (i >= dlist->used) return (NULL);

  elem = (dirlist_elem_t *)array_get_offset(dlist, i);
  if (elem->path)
  {
    (*idx_ptr)++;
    return (elem);
  }
  return (NULL);
}

void
listfiles_cleanup(dirlist_t *dlist)
{
  size_t i = 0;
  dirlist_elem_t *elem = NULL;

  if (!dlist) return;
  while( (elem = listfiles_foreach(dlist, &i)) != NULL )
  {
    free((void *)elem->ext);
    free_string(elem->path);
    if (elem->data) free_string(elem->data);
  }
  array_free(dlist);
}

